/*
    日志落地模块：
    功能：将格式化完成后的日志消息字符串，输出到指定的位置
    扩展：支持同时将日志落地到不同的位置
    位置有：1、标准输出 2、指定文件（事后对进行日志分析） 3、滚动文件（文件按照时间/大小进行滚动切换）
    扩展：支持落地方向的扩展
        用户可以自己编写一个新的落地模块，将日志进行其他方向的落地
    实现思想：
        1、抽象出落地模块类
        2、不同落地方向从基类进行派生
        3、使用工厂模式进行创建与表示分离
*/

#ifndef __M_LOGSINK_H__
#define __M_LOGSINK_H__
#include "util.hpp"
#include <memory>
#include <fstream>
#include <cassert>
#include <sstream>

namespace Hxy_Log
{
    // 1.日志抽象类
    class LogSink
    {
        public:
        using ptr = std::shared_ptr<LogSink>;
        virtual ~LogSink(){}
        virtual void log(const char* data,size_t len) = 0;
    };
    // 2.派生出不同落地方式日志子类
    //   落地方向：标准输入写入
    class StdOutSink : public LogSink
    {
        public:
        virtual void log(const char* data,size_t len)
        {
            //往标准输出流输入，但是不能使用<<需要以'\0'结尾
            std::cout.write(data,len);
        }
    };
    //   落地方向：指定文件写入
    class FileSink : public LogSink
    {
        public:
        //需要传入指定文件的文件路径,并打开文件，把操作句柄管理起来
        FileSink(const std::string& pathname)
            :_pathname(pathname)
        {
            //1.通过给定文件路径进行创建文件
            util::file::CreateDirectory(util::file::Path(_pathname));
            //2.创建完文件后进行打开文件
            //打开一个文件，给定路径还有打开后写入的方式，以二进制和追加的方式
            _ofs.open(_pathname,std::ios::binary | std::ios::app);  
            assert(_ofs.is_open());
        }
        virtual void log(const char* data,size_t len)
        {
            _ofs.write(data,len);
            assert(_ofs.good());
        }
        private:
            std::string _pathname;  //文件路径名称
            std::ofstream _ofs; //操作句柄
    };
    //   落地方向：滚动文件写入(按照文件大小滚动)
    class RollFillSizeSink : public LogSink
    {
        public:
        // 需要传入基础文件名，还有最大限度的文件大小
        RollFillSizeSink(const std::string& basename,size_t max_fsize)
            :_basename(basename),_max_fsize(max_fsize),_cur_fsize(0),_file_count(0)
        {
            //将基础文件名构造成带时间的文件名
            std::string pathname = _CreateNewFile();
            //1.通过给定文件路径进行创建文件
            util::file::CreateDirectory(util::file::Path(pathname));
            //2.创建完文件后进行打开文件
            //打开一个文件，给定路径还有打开后写入的方式，以二进制和追加的方式
            _ofs.open(pathname,std::ios::binary | std::ios::app);  
            assert(_ofs.is_open());
        }
        virtual void log(const char* data,size_t len)
        {
            //当前文件写满了进行重新创建文件
            if(_cur_fsize>=_max_fsize)
            {
                _ofs.close();   //一定要进行关闭，不然会造成系统资源泄漏
                std::string pathname = _CreateNewFile();
                _ofs.open(pathname,std::ios::binary | std::ios::app);  
                assert(_ofs.is_open());
                _cur_fsize = 0;     //创建完新文件需要把当前大小置0
            }
            _ofs.write(data,len);
            assert(_ofs.is_open());
            _cur_fsize+=len;    //增加当前文件大小
        }
        private:
        std::string _CreateNewFile()
        {
            time_t t = util::date::GetTime();
            struct tm tm;
            localtime_r(&t,&tm);
            std::stringstream ss;
            ss<<_basename;
            ss<<tm.tm_year;
            ss<<tm.tm_mon;
            ss<<tm.tm_mday;
            ss<<tm.tm_hour;
            ss<<tm.tm_min;
            ss<<tm.tm_sec;
            ss<<"-";
            ss<<_file_count++;
            ss<<".log";
            return ss.str();
        }
        private:
            //通过基础文件名 + 扩展文件名（以时间大小生产）组成一个实际的当前输出的文件民
            size_t _file_count;
            std::string _basename;  //基础文件名   // ./abc/base-    ->   ./abc/base-202411172153.log
            std::ofstream _ofs;  //操作句柄
            size_t _max_fsize;  //最大文件大小  --  当前文件大小超过最大文件大小则进行切换文件
            size_t _cur_fsize;  //当前文件大小
    };
    // 3.简单工厂设计模式管理

    class LogSinkFactory
    {
        public:
        template<typename SinkType,typename ...Args>
        static LogSink::ptr create(Args&& ...args)
        {
            //简单工厂模式 -- 
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };
}
#endif